/**
 * Apache License Version 2.0, January 2004 http://www.apache.org/licenses/
 *
 * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 *
 * 1. Definitions.
 *
 * "License" shall mean the terms and conditions for use, reproduction, and distribution as defined
 * by Sections 1 through 9 of this document.
 *
 * "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is
 * granting the License.
 *
 * "Legal Entity" shall mean the union of the acting entity and all other entities that control, are
 * controlled by, or are under common control with that entity. For the purposes of this definition,
 * "control" means (i) the power, direct or indirect, to cause the direction or management of such
 * entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
 * outstanding shares, or (iii) beneficial ownership of such entity.
 *
 * "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this
 * License.
 *
 * "Source" form shall mean the preferred form for making modifications, including but not limited
 * to software source code, documentation source, and configuration files.
 *
 * "Object" form shall mean any form resulting from mechanical transformation or translation of a
 * Source form, including but not limited to compiled object code, generated documentation, and
 * conversions to other media types.
 *
 * "Work" shall mean the work of authorship, whether in Source or Object form, made available under
 * the License, as indicated by a copyright notice that is included in or attached to the work (an
 * example is provided in the Appendix below).
 *
 * "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or
 * derived from) the Work and for which the editorial revisions, annotations, elaborations, or other
 * modifications represent, as a whole, an original work of authorship. For the purposes of this
 * License, Derivative Works shall not include works that remain separable from, or merely link (or
 * bind by name) to the interfaces of, the Work and Derivative Works thereof.
 *
 * "Contribution" shall mean any work of authorship, including the original version of the Work and
 * any modifications or additions to that Work or Derivative Works thereof, that is intentionally
 * submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or
 * Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this
 * definition, "submitted" means any form of electronic, verbal, or written communication sent to
 * the Licensor or its representatives, including but not limited to communication on electronic
 * mailing lists, source code control systems, and issue tracking systems that are managed by, or on
 * behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding
 * communication that is conspicuously marked or otherwise designated in writing by the copyright
 * owner as "Not a Contribution."
 *
 * "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a
 * Contribution has been received by Licensor and subsequently incorporated within the Work.
 *
 * 2. Grant of Copyright License. Subject to the terms and conditions of this License, each
 * Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
 * irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display,
 * publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or
 * Object form.
 *
 * 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor
 * hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 * (except as stated in this section) patent license to make, have made, use, offer to sell, sell,
 * import, and otherwise transfer the Work, where such license applies only to those patent claims
 * licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or
 * by combination of their Contribution(s) with the Work to which such Contribution(s) was
 * submitted. If You institute patent litigation against any entity (including a cross-claim or
 * counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work
 * constitutes direct or contributory patent infringement, then any patent licenses granted to You
 * under this License for that Work shall terminate as of the date such litigation is filed.
 *
 * 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works
 * thereof in any medium, with or without modifications, and in Source or Object form, provided that
 * You meet the following conditions:
 *
 * (a) You must give any other recipients of the Work or Derivative Works a copy of this License;
 * and
 *
 * (b) You must cause any modified files to carry prominent notices stating that You changed the
 * files; and
 *
 * (c) You must retain, in the Source form of any Derivative Works that You distribute, all
 * copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding
 * those notices that do not pertain to any part of the Derivative Works; and
 *
 * (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative
 * Works that You distribute must include a readable copy of the attribution notices contained
 * within such NOTICE file, excluding those notices that do not pertain to any part of the
 * Derivative Works, in at least one of the following places: within a NOTICE text file distributed
 * as part of the Derivative Works; within the Source form or documentation, if provided along with
 * the Derivative Works; or, within a display generated by the Derivative Works, if and wherever
 * such third-party notices normally appear. The contents of the NOTICE file are for informational
 * purposes only and do not modify the License. You may add Your own attribution notices within
 * Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the
 * Work, provided that such additional attribution notices cannot be construed as modifying the
 * License.
 *
 * You may add Your own copyright statement to Your modifications and may provide additional or
 * different license terms and conditions for use, reproduction, or distribution of Your
 * modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and
 * distribution of the Work otherwise complies with the conditions stated in this License.
 *
 * 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution
 * intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms
 * and conditions of this License, without any additional terms or conditions. Notwithstanding the
 * above, nothing herein shall supersede or modify the terms of any separate license agreement you
 * may have executed with Licensor regarding such Contributions.
 *
 * 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service
 * marks, or product names of the Licensor, except as required for reasonable and customary use in
 * describing the origin of the Work and reproducing the content of the NOTICE file.
 *
 * 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor
 * provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation,
 * any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 * PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or
 * redistributing the Work and assume any risks associated with Your exercise of permissions under
 * this License.
 *
 * 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including
 * negligence), contract, or otherwise, unless required by applicable law (such as deliberate and
 * grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for
 * damages, including any direct, indirect, special, incidental, or consequential damages of any
 * character arising as a result of this License or out of the use or inability to use the Work
 * (including but not limited to damages for loss of goodwill, work stoppage, computer failure or
 * malfunction, or any and all other commercial damages or losses), even if such Contributor has
 * been advised of the possibility of such damages.
 *
 * 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works
 * thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty,
 * indemnity, or other liability obligations and/or rights consistent with this License. However, in
 * accepting such obligations, You may act only on Your own behalf and on Your sole responsibility,
 * not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each
 * Contributor harmless for any liability incurred by, or claims asserted against, such Contributor
 * by reason of your accepting any such warranty or additional liability.
 *
 * END OF TERMS AND CONDITIONS
 *
 * APPENDIX: How to apply the Apache License to your work.
 *
 * To apply the Apache License to your work, attach the following boilerplate notice, with the
 * fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include
 * the brackets!)  The text should be enclosed in the appropriate comment syntax for the file
 * format. We also recommend that a file or class name and description of purpose be included on the
 * same "printed page" as the copyright notice for easier identification within third-party
 * archives.
 *
 * Copyright 2016 Alibaba Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package com.taobao.weex.dom;

import android.graphics.Canvas;
import android.graphics.Typeface;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.UnderlineSpan;
import android.util.Log;

import com.taobao.weex.WXEnvironment;
import com.taobao.weex.WXSDKManager;
import com.taobao.weex.common.Constants;
import com.taobao.weex.dom.flex.CSSConstants;
import com.taobao.weex.dom.flex.CSSNode;
import com.taobao.weex.dom.flex.FloatUtil;
import com.taobao.weex.dom.flex.MeasureOutput;
import com.taobao.weex.ui.component.WXComponent;
import com.taobao.weex.ui.component.WXText;
import com.taobao.weex.ui.component.WXTextDecoration;
import com.taobao.weex.utils.WXDomUtils;
import com.taobao.weex.utils.WXLogUtils;
import com.taobao.weex.utils.WXResourceUtils;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import static com.taobao.weex.dom.WXStyle.UNSET;

/**
 * Created by Carry on 2017/6/16.
 */

public class RichTextDomObject extends WXDomObject {
    /**
     * Object for calculating text's width and height. This class is an anonymous class of
     * implementing {@link com.taobao.weex.dom.flex.CSSNode.MeasureFunction}
     */
    /**
     * package
     **/
    static final CSSNode.MeasureFunction TEXT_MEASURE_FUNCTION = new CSSNode.MeasureFunction() {
        @Override
        public void measure(CSSNode node, float width, @NonNull MeasureOutput measureOutput) {
            RichTextDomObject textDomObject = (RichTextDomObject) node;
            if (CSSConstants.isUndefined(width)) {
                width = node.cssstyle.maxWidth;
            }
            if (textDomObject.getTextWidth(textDomObject.mTextPaint, width, false) > 0) {
                textDomObject.layout = textDomObject.createLayout(width, false, null);
                textDomObject.hasBeenMeasured = true;
                textDomObject.previousWidth = textDomObject.layout.getWidth();
                measureOutput.height = textDomObject.layout.getHeight();
                measureOutput.width = textDomObject.previousWidth;
            } else {
                measureOutput.height = 0;
                measureOutput.width = 0;
            }
        }
    };

    private static class SetSpanOperation {

        protected final int start, end, flag;
        protected final Object what;

        SetSpanOperation(int start, int end, Object what) {
            this(start, end, what, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
        }

        SetSpanOperation(int start, int end, Object what, int flag) {
            this.start = start;
            this.end = end;
            this.what = what;
            this.flag = flag;
        }

        public void execute(Spannable sb) {
            sb.setSpan(what, start, end, flag);
        }
    }


    private static final Canvas DUMMY_CANVAS = new Canvas();
    private static final String ELLIPSIS = "\u2026";
    private boolean mIsColorSet = false;
    private boolean hasBeenMeasured = false;
    private int mColor;
    /**
     * mFontStyle can be {@link Typeface#NORMAL} or {@link Typeface#ITALIC}.
     */
    private int mFontStyle = UNSET;
    /**
     * mFontWeight can be {@link Typeface#NORMAL} or {@link Typeface#BOLD}.
     */
    private int mFontWeight = UNSET;
    private int mNumberOfLines = UNSET;
    private int mFontSize = UNSET;
    private int mLineHeight = UNSET;
    private float previousWidth = Float.NaN;
    private String mFontFamily = null;
    private String mText = null;
    private TextUtils.TruncateAt textOverflow;
    private Layout.Alignment mAlignment;
    private WXTextDecoration mTextDecoration = WXTextDecoration.NONE;
    private TextPaint mTextPaint = new TextPaint();
    private
    @Nullable
    Spanned spanned;
    private
    @Nullable
    Layout layout;
    private AtomicReference<Layout> atomicReference = new AtomicReference<>();

    /**
     * Create an instance of current class, and set {@link #TEXT_MEASURE_FUNCTION} as the
     * measureFunction
     *
     * @see CSSNode#setMeasureFunction(MeasureFunction)
     */
    public RichTextDomObject() {
        super();
        mTextPaint.setFlags(TextPaint.ANTI_ALIAS_FLAG);
        setMeasureFunction(TEXT_MEASURE_FUNCTION);
    }

    public TextPaint getTextPaint() {
        return mTextPaint;
    }

    /**
     * Prepare the text {@link Spanned} for calculating text's size. This is done by setting various
     * text span to the text.
     *
     * @see android.text.style.CharacterStyle
     */
    @Override
    public void layoutBefore() {
        hasBeenMeasured = false;
        updateStyleAndText();
        mText = getText();
        spanned = createSpanned(mText);
        super.dirty();
        super.layoutBefore();
    }

    @Override
    public void layoutAfter() {
        if (hasBeenMeasured) {
            if (layout != null &&
                    !FloatUtil.floatsEqual(WXDomUtils.getContentWidth(this), previousWidth)) {
                recalculateLayout();
            }
        } else {
            updateStyleAndText();
            recalculateLayout();
        }
        hasBeenMeasured = false;
        if (layout != null && !layout.equals(atomicReference.get()) &&
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //TODO Warm up, a profile should be used to see the improvement.
            warmUpTextLayoutCache(layout);
        }
        swap();
        super.layoutAfter();
    }

    @Override
    public Layout getExtra() {
        return atomicReference.get();
    }

    @Override
    public void updateAttr(Map<String, Object> attrs) {
        swap();
        super.updateAttr(attrs);
        mText = getText();
    }

    @Override
    public void updateStyle(Map<String, Object> styles) {
        swap();
        super.updateStyle(styles);
        updateStyleImp(styles);
    }

    @Override
    public RichTextDomObject clone() {
        RichTextDomObject dom = null;
        try {
            dom = new RichTextDomObject();
            copyFields(dom);
            dom.hasBeenMeasured = hasBeenMeasured;
            dom.atomicReference = atomicReference;
        } catch (Exception e) {
            if (WXEnvironment.isApkDebugable()) {
                WXLogUtils.e("WXTextDomObject clone error: ", e);
            }
        }
        if (dom != null) {
            dom.spanned = spanned;
        }
        return dom;
    }

    /**
     * RecalculateLayout.
     */
    private void recalculateLayout() {
        float contentWidth = WXDomUtils.getContentWidth(this);
        if (contentWidth > 0) {
            mText = getText();
            spanned = createSpanned(mText);
            layout = createLayout(contentWidth, true, layout);
            previousWidth = layout.getWidth();
        }
    }

    /**
     * Update style and text.
     */
    private void updateStyleAndText() {
        updateStyleImp(getStyles());
        mText = getText();
    }


    private String getText() {
        WXComponent wxComponent = WXSDKManager.getInstance().getWXRenderManager().getWXComponent
                (getDomContext().getInstanceId(), mRef);
        if (wxComponent != null) {
            return wxComponent.getRichSpanned();
        }
        return null;
    }

    /**
     * Record the property according to the given style
     *
     * @param style the give style.
     */
    private void updateStyleImp(Map<String, Object> style) {
        if (style != null) {
            if (style.containsKey(Constants.Name.LINES)) {
                int lines = WXStyle.getLines(style);
                //修改改变lines为0时的bug
                if (lines >= 0) {
                    mNumberOfLines = lines;
                }
            }
            if (style.containsKey(Constants.Name.FONT_SIZE)) {
                mFontSize = WXStyle.getFontSize(style, getViewPortWidth());
            }
            if (style.containsKey(Constants.Name.FONT_WEIGHT)) {
                mFontWeight = WXStyle.getFontWeight(style);
            }
            if (style.containsKey(Constants.Name.FONT_STYLE)) {
                mFontStyle = WXStyle.getFontStyle(style);
            }
            if (style.containsKey(Constants.Name.COLOR)) {
                mColor = WXResourceUtils.getColor(WXStyle.getTextColor(style));
                mIsColorSet = mColor != Integer.MIN_VALUE;
            }
            if (style.containsKey(Constants.Name.TEXT_DECORATION)) {
                mTextDecoration = WXStyle.getTextDecoration(style);
            }
            if (style.containsKey(Constants.Name.FONT_FAMILY)) {
                mFontFamily = WXStyle.getFontFamily(style);
            }
            mAlignment = WXStyle.getTextAlignment(style);
            textOverflow = WXStyle.getTextOverflow(style);
            int lineHeight = WXStyle.getLineHeight(style, getViewPortWidth());
            if (lineHeight != UNSET) {
                mLineHeight = lineHeight;
            }
        }
    }

    /**
     * Update layout according to {@link #mText} and span
     *
     * @param width          the specified width.
     * @param forceWidth     If true, force the text width to the specified width, otherwise, text
     *                       width may equals to or be smaller than the specified width.
     * @param previousLayout the result of previous layout, could be null.
     */
    private
    @NonNull
    Layout createLayout(float width, boolean forceWidth, @Nullable Layout previousLayout) {
        mText = getText();
        float textWidth;
        textWidth = getTextWidth(mTextPaint, width, forceWidth);
        Layout layout;
        if (!FloatUtil.floatsEqual(previousWidth, textWidth) || previousLayout == null) {
            layout = new StaticLayout(spanned, mTextPaint, (int) Math.ceil(textWidth),
                    Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
        } else {
            layout = previousLayout;
        }
        if (mNumberOfLines != UNSET && mNumberOfLines > 0 && mNumberOfLines < layout.getLineCount
                ()) {
            int lastLineStart, lastLineEnd;
            lastLineStart = layout.getLineStart(mNumberOfLines - 1);
            lastLineEnd = layout.getLineEnd(mNumberOfLines - 1);
            if (lastLineStart < lastLineEnd) {
                String text = truncate(mText.substring(0, lastLineEnd), mTextPaint, layout
                        .getWidth(), textOverflow);
                spanned = createSpanned(text);
                return new StaticLayout(spanned, mTextPaint, (int) Math.ceil(textWidth),
                        Layout.Alignment.ALIGN_NORMAL, 1, 0, false);
            }
        }
        return layout;
    }

    @NonNull
    private String truncate(@Nullable String source, @NonNull TextPaint paint,
                            int desired, @Nullable TextUtils.TruncateAt truncateAt) {
        if (!TextUtils.isEmpty(source)) {
            StringBuilder builder;
            Spanned spanned;
            StaticLayout layout;
            for (int i = source.length(); i > 0; i--) {
                builder = new StringBuilder(i + 1);
                builder.append(source, 0, i);
                if (truncateAt != null) {
                    builder.append(ELLIPSIS);
                }
                spanned = createSpanned(builder.toString());
                layout = new StaticLayout(spanned, paint, desired, Layout.Alignment.ALIGN_NORMAL,
                        1, 0, true);
                if (layout.getLineCount() <= 3) {
                    return spanned.toString();
                }
            }
        }
        return "";
    }

    /**
     * Get text width according to constrain of outerWidth with and forceToDesired
     * @param textPaint paint used to measure text
     * @param outerWidth the width that css-layout desired.
     * @param forceToDesired if set true, the return value will be outerWidth, no matter what the
     *                       width
     *                   of text is.
     * @return if forceToDesired is false, it will be the minimum value of the width of text and
     * outerWidth in case of outerWidth is defined, in other case, it will be outer width.
     */
    /**
     * package
     **/
    private float getTextWidth(TextPaint textPaint, float outerWidth, boolean forceToDesired) {
        float textWidth;
        if (forceToDesired) {
            textWidth = outerWidth;
        } else {
            float desiredWidth = Layout.getDesiredWidth(spanned, textPaint);
            if (CSSConstants.isUndefined(outerWidth) || desiredWidth < outerWidth) {
                textWidth = desiredWidth;
            } else {
                textWidth = outerWidth;
            }
        }
        return textWidth;
    }

    /**
     * Update {@link #spanned} according to the give charSequence and styles
     *
     * @param text the give raw text.
     * @return an Spanned contains text and spans
     */
    protected
    @NonNull
    Spanned createSpanned(String text) {
        if (!TextUtils.isEmpty(text)) {
            SpannableString spannable = new SpannableString(text);
            updateSpannable(spannable, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
            return spannable;
        }
        return new SpannableString("");
    }

    private void updateSpannable(Spannable spannable, int spanFlag) {
        List<RichTextDomObject.SetSpanOperation> ops = createSetSpanOperation(spannable.length(),
                spanFlag);
        if (mFontSize == UNSET) {
            ops.add(new SetSpanOperation(0, spannable.length(),
                    new AbsoluteSizeSpan(WXText.sDEFAULT_SIZE), spanFlag));
        }
        Collections.reverse(ops);
        for (SetSpanOperation op : ops) {
            op.execute(spannable);
        }

        update(spannable, spanFlag);
    }

    private List<SetSpanOperation> createSetSpanOperation(int end, int spanFlag) {
        List<SetSpanOperation> ops = new LinkedList<>();
        int start = 0;
        if (end >= start) {
            if (mTextDecoration == WXTextDecoration.UNDERLINE) {
                ops.add(new SetSpanOperation(start, end,
                        new UnderlineSpan(), spanFlag));
            }
            if (mTextDecoration == WXTextDecoration.LINETHROUGH) {
                ops.add(new SetSpanOperation(start, end,
                        new StrikethroughSpan(), spanFlag));
            }
            if (mIsColorSet) {
                ops.add(new SetSpanOperation(start, end,
                        new ForegroundColorSpan(mColor), spanFlag));
            }
            if (mFontSize != UNSET) {
                ops.add(new SetSpanOperation(start, end, new AbsoluteSizeSpan(mFontSize),
                        spanFlag));
            }
            if (mFontStyle != UNSET
                    || mFontWeight != UNSET
                    || mFontFamily != null) {
                ops.add(new SetSpanOperation(start, end,
                        new WXCustomStyleSpan(mFontStyle, mFontWeight, mFontFamily),
                        spanFlag));
            }
            ops.add(new SetSpanOperation(start, end, new AlignmentSpan.Standard(mAlignment),
                    spanFlag));
            if (mLineHeight != UNSET) {
                ops.add(new SetSpanOperation(start, end, new WXLineHeightSpan(mLineHeight),
                        spanFlag));
            }
        }
        return ops;
    }

    private void update(Spannable spannable, int spanFlag) {
        WXComponent wxComponent = WXSDKManager.getInstance().getWXRenderManager().getWXComponent
                (getDomContext().getInstanceId(), mRef);
        if (wxComponent != null) {
            List<BMRichSpan> spans = wxComponent.getSpans();
            for (BMRichSpan span : spans) {
                Log.e("rich", span.toString());
                int start = span.getStart();
                int end = span.getEnd();
                if (start > spannable.length()) {
                    start = spannable.length();
                }
                if (end > spannable.length()) {
                    end = spannable.length();
                }
                spannable.setSpan(span.getSpan(), start, end, spanFlag);
            }
        }
    }

    /**
     * Move the reference of current layout to the {@link AtomicReference} for further use, then
     * clear current layout.
     */
    private void swap() {
        if (layout != null) {
            atomicReference.set(layout);
            layout = null;
            mTextPaint = new TextPaint(mTextPaint);
        }
    }

    /**
     * As warming up TextLayoutCache done in the DOM thread may manipulate UI operation, there may
     * be some exception, in which case the exception is ignored. After all, this is just a warm up
     * operation.
     *
     * @return false for warm up failure, otherwise returns true.
     */
    private boolean warmUpTextLayoutCache(Layout layout) {
        boolean result;
        try {
            layout.draw(DUMMY_CANVAS);
            result = true;
        } catch (Exception e) {
            WXLogUtils.eTag(TAG, e);
            result = false;
        }
        return result;
    }


    public static class BMRichSpan {
        private Object span;
        private int start;
        private int end;

        public BMRichSpan(Object span, int start, int end) {
            this.span = span;
            this.start = start;
            this.end = end;
        }

        public Object getSpan() {
            return span;
        }

        public void setSpan(CharacterStyle span) {
            this.span = span;
        }

        public int getStart() {
            return start;
        }

        public void setStart(int start) {
            this.start = start;
        }

        public int getEnd() {
            return end;
        }

        public void setEnd(int end) {
            this.end = end;
        }

        @Override
        public String toString() {
            return "BMRichSpan{" +
                    "span=" + span +
                    ", start=" + start +
                    ", end=" + end +
                    '}';
        }
    }
}
